package com.commons.utils;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/*
*ApplicationContextAware:Bean在被初始之后，可以取得一些相对应的资源，
* 这个类可以直接获取spring 配置文件中 所有引用（注入）到的bean对象。
*DisposableBean:为bean提供了释放资源方法的方式，它只包括destroy方法，凡是继承该接口的类，在bean被销毁之前都会执行该方法。
 */

public class SpringContextHolder implements ApplicationContextAware,DisposableBean {

    private static final Logger LOGGER=LoggerFactory.getLogger(SpringContextHolder.class);

    private static ApplicationContext applicationContext=null;
    private static final List<CallBack> CALL_BACKS=new ArrayList<>();
    private static boolean addCallBack=true;

    /**
     * 针对某些初始化方法，在SpringContextHolder未初始化时，提交回调方法
     * @param callBack
     */
    private synchronized static void addCallBacks(CallBack callBack){
        if (addCallBack){
            SpringContextHolder.CALL_BACKS.add(callBack);
        }else {
            LOGGER.info("Callback:{}已无法添加！立即执行!",callBack.getCallBackName());
            callBack.executor();
        }
    }

    /**
     * 获取Bean
     * @param BeanName
     * @throws BeansException
     */
    public static <T> T getBean(String BeanName){
        assertContextInjected();
        return (T) applicationContext.getBean(BeanName);
    }

    /**
     * 从静态变量获取bean
     * @param requiredType
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> requiredType){
        assertContextInjected();
        return applicationContext.getBean(requiredType);
    }

    public static <T> T getProperties(String property,T defaultValue,Class<T> requiredType){
        T result=defaultValue;
        try {
            result=getBean(Environment.class).getProperty(property,requiredType);
        }catch (Exception ignore){

        }
        return result;
    }

    /**
     * 获取Springboot配置信息
     * @param property
     * @return
     */
    public static String getProperties(String property) {
        return getProperties(property, null, String.class);
    }

    /**
     *  获取SpringBoot 配置信息
     * @param property 属性key
     * @param requiredType 返回类型
     * @param <T>
     * @return
     */
    public static <T> T getProperties(String property,Class<T> requiredType){
        return getProperties(property,null,requiredType);
    }

    /**
     * 检查applicationContext不为空
     */
    private static void assertContextInjected(){
        if(applicationContext==null){
            throw new IllegalStateException("applicaitonContext属性未注入, 请在applicationContext" +
                    ".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder.");

        }
    }

    public static void clearHolder(){
        LOGGER.debug("清除SpringContextHolder中的ApplicationContext:{}",applicationContext);
        applicationContext=null;
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if(SpringContextHolder.applicationContext!=null){
            LOGGER.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:{}" , SpringContextHolder.applicationContext);
        }
        SpringContextHolder.applicationContext=applicationContext;
        if(addCallBack){
            for(CallBack callBack:SpringContextHolder.CALL_BACKS){
                callBack.executor();
            }
            CALL_BACKS.clear();
        }
        SpringContextHolder.addCallBack=false;
    }

    @Override
    public void destroy() throws Exception {
        SpringContextHolder.clearHolder();
    }


    /**
     * 获取所有@Service的Bean名称
     * @return
     */
    public static List<String> getAllServiceName(){
        return  new ArrayList<>(Arrays.asList(applicationContext.getBeanNamesForAnnotation(Service.class)));
    }
}
